package de.stefanocke.japkit.support

import de.stefanocke.japkit.gen.GenAnnotationMirror
import de.stefanocke.japkit.gen.GenAnnotationValue
import de.stefanocke.japkit.gen.GenExtensions
import de.stefanocke.japkit.metaannotations.AVMappingMode
import de.stefanocke.japkit.metaannotations.AnnotationMappingMode
import de.stefanocke.japkit.metaannotations.DefaultAnnotation
import de.stefanocke.japkit.support.el.ELSupport
import java.util.List
import java.util.Map
import java.util.Set
import javax.annotation.processing.ProcessingEnvironment
import javax.lang.model.element.AnnotationMirror
import javax.lang.model.element.Element
import javax.lang.model.type.DeclaredType

import static de.stefanocke.japkit.metaannotations.AnnotationMappingMode.*

@Data
class AnnotationMappingRule {
	val extension ElementsExtensions jme = ExtensionRegistry.get(ElementsExtensions)
	val extension ProcessingEnvironment procEnv = ExtensionRegistry.get(ProcessingEnvironment)
	//val extension RoundEnvironment roundEnv = ExtensionRegistry.get(RoundEnvironment)
	val extension ELSupport elSupport = ExtensionRegistry.get(ELSupport)
	val extension MessageCollector messageCollector = ExtensionRegistry.get(MessageCollector)
	val extension AnnotationExtensions annotationExtensions = ExtensionRegistry.get(AnnotationExtensions)
	val extension RuleFactory =  ExtensionRegistry.get(RuleFactory)
	val extension TypesExtensions = ExtensionRegistry.get(TypesExtensions)

	String id
	ElementMatcher elementMatcher
	DeclaredType targetAnnotation
	AnnotationValueMappingRule[] valueMappings
	AnnotationMappingMode mode
	DeclaredType asAvAnnotation
	String asAvAnnotationValueName
	AVMappingMode asAvMode
	Set<String> copyAnnotationsFqns
	String[] copyAnnotationsFromPackages
	boolean setShadowOnTriggerAnnotations
	

	def private appliesTo(Element e) {
		elementMatcher.matches(e)
	}

	/**
	 * Adds the annotation mapped by this rule.
	 * 
	 * @param annotations the annotations generated so far for the element
	 * @param srcElement the source element
	 */
	def void mapOrCopyAnnotations(List<GenAnnotationMirror> annotations, Element srcElement, Map<String, AnnotationMappingRule> mappingsWithId) {
		mapOrCopyAnnotations(annotations, srcElement, mappingsWithId, false)
	}
	
	def void mapOrCopyAnnotations(List<GenAnnotationMirror> annotations, Element srcElement, Map<String, AnnotationMappingRule> mappingsWithId, 
		 boolean srcElementChanged
	) {
		if(!appliesTo(srcElement)){
			return
		}
		
		copyAnnotations(srcElement, annotations)
		

		if(!DefaultAnnotation.name.equals(targetAnnotation?.qualifiedName)){
			mapAnnotation(srcElementChanged, srcElement, annotations, mappingsWithId)	
		}

	}
	
	def private copyAnnotations(Element srcElement, List<GenAnnotationMirror> annotations) {
		if(!copyAnnotationsFqns.empty || !copyAnnotationsFromPackages.empty){
			srcElement.annotationMirrors.filter[shallCopyAnnotation].forEach[
				try{
					annotations.add(copyAnnotation)			
				} catch(ProcessingException e){
					messageCollector.reportError(e)
				}
			]
		}
	}
	
	private def copyAnnotation(AnnotationMirror am) {
		GenExtensions.copy(am) => [
				if(setShadowOnTriggerAnnotations){setShadowIfAppropriate}
			]
	}
	
	def private boolean shallCopyAnnotation(AnnotationMirror am){
		copyAnnotationsFqns.contains(am.annotationType.qualifiedName) 
		|| {
			val packageFqn = am.annotationType.asElement.package.qualifiedName.toString
			copyAnnotationsFromPackages.exists[
				equals(packageFqn) ||
				equals("*") || 
				endsWith(".*") && packageFqn.equals(substring(0, it.length-2)) || 
				endsWith(".**") && packageFqn.startsWith(substring(0, it.length-3))
			]
		}
	}
	
	def private void mapAnnotation(boolean srcElementChanged, Element srcElement, List<GenAnnotationMirror> annotations, Map<String, AnnotationMappingRule> mappingsWithId) {
		
		
		var am = annotations.findFirst[hasFqn(targetAnnotation.qualifiedName)]
		
		if (am == null) {
			if (mode == REMOVE) {
				return
			} else if (mode == AS_ANNOTATION_VALUE) {
				am = asAnnotationValue(annotations, srcElement)
			} else {
				am = new GenAnnotationMirror(targetAnnotation)
				annotations.add(am)
			}
		} else {
			if(id.nullOrEmpty){
				switch (mode) {
					case ERROR_IF_EXISTS:
						throw new ProcessingException(
							'''The annotation «targetAnnotation.qualifiedName» was already generated by another rule and the mapping mode is «mode».''',
							srcElement)
					case REPLACE: {
						annotations.remove(am)
						am = new GenAnnotationMirror(targetAnnotation)
						annotations.add(am)
					}
					case REMOVE: {
						annotations.remove(am);
						return
					}
					case MERGE: { /**Reuse existing one */
					}
					case IGNORE:
						return
					case AS_ANNOTATION_VALUE: {
						am = asAnnotationValue(annotations, srcElement)
			
					}
					default:
						throw new ProcessingException('''Annotation mapping mode «mode» is not supported.''', srcElement)
				}
			
			} else {
				//The AnnotationMapping is used form an annotation value mapping. Just ignore the mapping mode and create a new annotation.
				am = new GenAnnotationMirror(targetAnnotation)
				annotations.add(am)
			}
		
		}
		
		val annotation = am
		
		//If the src element has not changed, use the ELContext of the parent annotation mapping.
		//Otherwise create an new one, where the properties of the parent context can be accessed by prefixing with "parent."
		//val elContext = if(parentElContext!=null && !srcElementChanged) parentElContext 
		//	else  srcElement.createElContextMap(parentElContext, #{"targetAnnotation"->am})
		
		valueStack.scope(srcElement) [ vs |
			vs.put("targetAnnotation", annotation)
		
			annotation => [
				valueMappings.forEach [ vm |
					try {
						setValue(vm.name,
							[ avType |
								vm.mapAnnotationValue(annotation, srcElement, avType, mappingsWithId)
							])
			
					} catch (RuntimeException e) {
			
						messageCollector.reportError('''
								Could not set annotation value «vm.name» for mapped annotation «it?.annotationType?.qualifiedName».
								Cause: «e.message»
								Annotation Mapping was triggered by: «_elementMatcher»
							''', e, srcElement, null, null)
					}
				]
			]
		
		]
	}
	

	/**
	 * The mapped annotation is an annotation value (or part of it, in case of arrays) in another annotation ("enclosing annotation"). 
	 */
	def GenAnnotationMirror asAnnotationValue(List<GenAnnotationMirror> annotations, Element srcElement) {
		try {
			var enclosingAnnotation = annotations.findFirst[hasFqn(asAvAnnotation.qualifiedName)]
			if (enclosingAnnotation == null) {
				enclosingAnnotation = new GenAnnotationMirror(asAvAnnotation)
				annotations.add(enclosingAnnotation)
			}

			val existingValue = enclosingAnnotation.getValueWithoutDefault(asAvAnnotationValueName)

			val am = switch (asAvMode) {
				case AVMappingMode.ERROR_IF_EXISTS: {
					if (existingValue != null) {
						throw new ProcessingException(
							'''The annotation value '«asAvAnnotationValueName»' in «enclosingAnnotation» was already generated by another rule and the annotation value mapping mode is «asAvMode».''',
							srcElement)
					}
					new GenAnnotationMirror(targetAnnotation)
				}
				case AVMappingMode.REPLACE: {
					new GenAnnotationMirror(targetAnnotation)
				}
				case AVMappingMode.JOIN_LIST: {
					new GenAnnotationMirror(targetAnnotation)
				}
				case AVMappingMode.MERGE: {
					if (existingValue?.value != null) {
						if (existingValue.value instanceof GenAnnotationMirror) {
							existingValue.value as GenAnnotationMirror
						} else
							throw new IllegalArgumentException(
								'''For mapping mode «asAvMode», the annotation value '«asAvAnnotationValueName»' must be an annotation, but it is «existingValue.
									value» (class:«existingValue.value.class»)''')
					} else
						new GenAnnotationMirror(targetAnnotation)

				}
				default:
					throw new ProcessingException('''Annotation value mapping mode «asAvMode» is not allowed here.''',
						srcElement)
			}

			val annotationAsAV = new GenAnnotationValue(am)

			//wenn der Zieltyp ein Array ist, sollten wir die Value besser gleich in eine Liste einpacken. Sihe AVMR
			if (asAvMode == AVMappingMode.JOIN_LIST) {
				if (existingValue == null) {
					enclosingAnnotation.setValue(asAvAnnotationValueName,
						[new GenAnnotationValue(newArrayList(annotationAsAV))])
				} else {
					if (existingValue.value instanceof List<?>) {
						(existingValue.value as List<GenAnnotationValue>).add(annotationAsAV)
					} else {
						enclosingAnnotation.setValue(asAvAnnotationValueName,
							[new GenAnnotationValue(newArrayList(existingValue, annotationAsAV))])
					}
				}

			} else {
				enclosingAnnotation.setValue(asAvAnnotationValueName, [annotationAsAV])
			}
			am
		} catch (RuntimeException e) {

			messageCollector.reportError(
				'''Could not set or add «targetAnnotation» as annotation value «asAvAnnotationValueName» of «asAvAnnotation».''', e, srcElement, null, null)
			null
		}
	}

	new(AnnotationMirror am) {
		_id = am.value("id", String)
		_elementMatcher = createElementMatcher(am)
		_targetAnnotation = am.value("targetAnnotation", DeclaredType)
		_valueMappings = am.value("valueMappings", typeof(AnnotationMirror[])).map[
			new AnnotationValueMappingRule(it)]
		_mode = am.value("mode", AnnotationMappingMode)
		_asAvAnnotation = am.value("asAvAnnotation", DeclaredType)
		_asAvAnnotationValueName = am.value("asAvAnnotationValueName", String)
		_asAvMode = am.value("asAvMode", AVMappingMode)
		_copyAnnotationsFqns = am.value("copyAnnotations", typeof(DeclaredType[])).map[qualifiedName].toSet
		_copyAnnotationsFromPackages = am.value("copyAnnotationsFromPackages", typeof(String[]))
		_setShadowOnTriggerAnnotations = am.value("setShadowOnTriggerAnnotations", Boolean)

	}
	
	
	
}
